package com.ppch.zerocodegenerator.ai.service_factory;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ppch.zerocodegenerator.ai.enums.CodeGenTypeEnum;
import com.ppch.zerocodegenerator.ai.guardrail.PromptSafetyInputGuardrail;
import com.ppch.zerocodegenerator.ai.service.AiCodeGeneratorService;
import com.ppch.zerocodegenerator.ai.tools.FileWriteTool;
import com.ppch.zerocodegenerator.exception.BusinessException;
import com.ppch.zerocodegenerator.exception.ErrorCode;
import com.ppch.zerocodegenerator.langgraph4j.utils.SpringContextUtil;
import com.ppch.zerocodegenerator.service.ChatHistoryService;
import dev.langchain4j.community.store.memory.chat.redis.RedisChatMemoryStore;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * Package:ZeroCodeGenerator
 * ClassName:AiCodeGeneratorServiceFactory
 *
 * @Author 泡泡茶壶
 * @Create 2025/10/6 18:29
 * @Version 1.0
 * Description:
 * AI Service 工厂
 */
@Configuration
@Slf4j
public class AiCodeGeneratorServiceFactory {

    /**
     * 通过配置文件自动装配对应的 ChatModel
     */
/*    @Resource
    private ChatModel chatModel;*/

    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;

    @Resource
    private ChatHistoryService chatHistoryService;

    /**
     * AiService实例缓存（同时缓存AiService实例以及对话记忆实例）
     * 缓存策略：
     * - 最大缓存 1000 个实例
     * - 写入后 30 分钟过期
     * - 访问后 10 分钟过期
     */
    private final Cache<String, AiCodeGeneratorService> serviceCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(Duration.ofMinutes(30))
            .expireAfterAccess(Duration.ofMinutes(10))
            .removalListener((key, value, cause) -> {
                log.debug("AI 服务实例被移除，缓存键为: {}, 原因: {}", key, cause);
            })
            .build();

    /**
     * 根据 appId 先从 Caffeine 本地缓存中获取 AiService 实例（兼容之前只采用appId作为缓存键的逻辑）
     * 如果缓存中没有则创建一个 AiService 实例。
     * @param appId 应用ID
     */
    public AiCodeGeneratorService getOrCreateAiCodeGeneratorService(long appId) {
        return getOrCreateAiCodeGeneratorService(appId, CodeGenTypeEnum.MULTI_FILE);
    }


    /**
     * 根据 appId、CodeGenTypeEnum 从 Caffeine 本地缓存中获取 AiService 实例
     * 如果缓存中没有则创建一个 AiService 实例。
     * @param appId 应用ID
     * @param codeGenTypeEnum 代码生成模式
     */
    public AiCodeGeneratorService getOrCreateAiCodeGeneratorService(long appId,CodeGenTypeEnum codeGenTypeEnum) {
        String cacheKey = buildCacheKey(appId,codeGenTypeEnum);
        return serviceCache.get(cacheKey, (key)-> createAiCodeGeneratorService(appId, codeGenTypeEnum));
    }


    /**
     * 根据 appId、CodeGenTypeEnum 构造缓存 Key
     * @param appId 应用ID
     * @param codeGenTypeEnum 代码生成模式
     */
    private String buildCacheKey(long appId, CodeGenTypeEnum codeGenTypeEnum) {
        return codeGenTypeEnum.getValue() + "_" + appId;
    }


    /**
     * 根据应用ID创建带有对话记忆能力的 AiService 实例（根据代码生成模式使用不同的AI大模型：deepseek-chat/deepseek-reasoner）
     * 1.一个应用拥有一个独立的 AiService 实例
     * 2.为了避免每次与 AI 对话都要创建一个 AiService 实例以及对话记忆实例 ，
     * 使用 Caffeine 本地缓存优化
     * 3.创建实例时，从数据库中根据应用ID加载对话历史到RedisChatMemoryStore中
     * @param appId 应用ID
     * @param codeGenTypeEnum 代码生成模式
     * @return AiCodeGeneratorService
     */
    public AiCodeGeneratorService createAiCodeGeneratorService(long appId, CodeGenTypeEnum codeGenTypeEnum) {
        log.info("为 appId: {} 创建新的 AI 服务实例", appId);
        // 1.根据 appId 构建独立的对话记忆存储（基于Redis的对话记忆存储）
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory
                .builder()
                .id(appId)
                .chatMemoryStore(redisChatMemoryStore)
                // 对话记忆保留的最大消息数
                .maxMessages(50)
                .build();
        // 2.加载对话历史到对话存储中
        chatHistoryService.loadChatHistoryToMemory(appId, chatMemory,50);
        // 3.根据代码生成模式使用不同的AI大模型（deepseek-chat/deepseek-reasoner）
        // 3.1创建 AiService 实例（deepseek-chat模型）
        // TODO：使用多例模式的 StreamingChatModel 解决并发问题
        StreamingChatModel streamingChatModel = SpringContextUtil.getBean("streamingChatModelPrototype", StreamingChatModel.class);
        AiCodeGeneratorService aiServiceWithDeepSeekChat = AiServices.builder(AiCodeGeneratorService.class)
                //.chatModel(chatModel)
                .streamingChatModel(streamingChatModel)
                .chatMemory(chatMemory)
                // 并发调用工具
                //.executeToolsConcurrently()
                // 输入护轨
                .inputGuardrails(new PromptSafetyInputGuardrail())
                // 设置在单次对话中连续调用工具的次数
                .maxSequentialToolsInvocations(20)
                .build();
        // 3.2创建 AiService 实例（deepseek-reasoner模型）
        /**
         * TODO：注意因为使用了 @MemoryId long appId 注解，将appId参数传递到工具内，这里只能使用chatMemoryProvider，
         *      否则报空指针.官网解释：https://docs.langchain4j.dev/tutorials/ai-services#chat-memory
         */
        // TODO：使用多例模式的 StreamingChatModel 解决并发问题
        StreamingChatModel reasoningStreamingChatModel = SpringContextUtil.getBean("reasoningStreamingChatModelPrototype", StreamingChatModel.class);
        AiCodeGeneratorService aiServiceWithDeepSeekReasoner = AiServices.builder(AiCodeGeneratorService.class)
                //.chatModel(chatModel)
                .streamingChatModel(reasoningStreamingChatModel)
                .chatMemoryProvider( memoryId  -> chatMemory)
                // 输入护轨
                .inputGuardrails(new PromptSafetyInputGuardrail())
                // 设置在单次对话中连续调用工具的次数
                .maxSequentialToolsInvocations(20)
                .tools(new FileWriteTool())
                //处理工具调用幻觉问题：即调用我没有提供的工具，直接报错提示 there is no tool called
                .hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
                        toolExecutionRequest, "Error: there is no tool called " + toolExecutionRequest.name()
                ))
                .build();
        return switch (codeGenTypeEnum){
            case HTML,MULTI_FILE -> aiServiceWithDeepSeekChat;
            case VUE_PROJECT -> aiServiceWithDeepSeekReasoner;
            default -> throw new BusinessException(ErrorCode.SYSTEM_ERROR, "不支持的代码生成类型: " + codeGenTypeEnum.getValue());
        };

    }

}
